package com.innjia.sys.service.impl;


import com.alipay.mychain.gl.biz.common.Response;
import com.alipay.mychain.gl.biz.sdk.Client;
import com.alipay.mychain.gl.biz.sdk.ClientConfig;
import com.alipay.mychain.gl.biz.sdk.ClientPropertyConfig;
import com.alipay.mychain.gl.common.crypto.*;
import com.alipay.mychain.gl.common.crypto.kdf.KeyPath;
import com.alipay.mychain.gl.common.crypto.kdf.KeyRepositry;
import com.alipay.mychain.gl.common.enums.PayloadType;
import com.alipay.mychain.gl.common.model.builder.TransactionBuilder;
import com.alipay.mychain.gl.common.model.entity.ContentOnlyNotaryPayloadDO;
import com.alipay.mychain.gl.common.model.entity.TransactionDO;
import com.alipay.mychain.gl.common.model.schema.TableSchemaData;
import org.springframework.stereotype.Service;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPublicKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeoutException;

@Service
public class QuKuaiUtil {

    /**
     * 创建 区块链
     *
     * @param accountLog
     * @return
     * @throws IOException
     * @throws TimeoutException
     */
    public String saveTransactionWrite(String accountLog) throws IOException, TimeoutException {
        Properties p = new Properties();
        p.load(new FileInputStream(QuKuaiUtil.class.getResource("/client.properties").getPath()));
        ClientConfig config = new ClientPropertyConfig(p);

        Client client = new Client(config);

//        String accountLog = "{'from':'Ailce', 'to':'Bob', 'value':'2000cent' ,'date':'2017- 10-10'}";
        TransactionDO tx = TransactionBuilder.getContentOnlyNotaryPayloadBuilder()
                .setContent(accountLog.getBytes("UTF-8"))//
                .setTimestamp(System.currentTimeMillis()).build();

        Response<TransactionDO> response = client.sendTransaction(tx);
        if (response.isSuccess()) {
            System.out.println("成功=------------------------------------");
//            Transaction Hash
            return tx.getTxHashValue();
        } else {
//                   rpc
            throw new RuntimeException("写入区块链失败");
        }

    }

    public String saveTransactionWrite2() throws IOException, TimeoutException {

        Properties p = new Properties();
        p.load(new FileInputStream(QuKuaiUtil.class.getResource("/client.properties").getPath()));
        ClientConfig config = new ClientPropertyConfig(p);

// 使用指定client配置初始化client
        Client client = new Client(config);

// 初始化链上加密key repositry
        KeyRepositry keyRepositry = new KeyRepositry();

// 设置虚拟root key
        KeyPath key1 = new KeyPath("/ABS_ROOT");
// 为BOC Bank类业务生成root加密key
        keyRepositry.setKey(key1.forName("ALIPAY").forName("BOC"), this.getKey());

// 一笔需要存证的账务流水
        String accountLog = "{'from':'Ailce', 'to':'Bob', 'value':'2000cent' ,'date':'2017-10-10'}";

// 存证数据加密key path
        String keyPathName = "/ABS_ROOT/ALIPAY/BOC/BILL";

// 根据key path生成加密key
        byte[] key = keyRepositry.getKeyFor(keyPathName);

// 设置链上数据的业务分类，用于将来从链上检索数据
// 该例子汇总，高4位0001表示某类业务，低4位表示业务子类型
        int catagory = 0x00010001;

// build transaction
        TransactionDO tx = TransactionBuilder.getEncryptShareNotaryPayloadBuilder()
                .encryptContent(accountLog.getBytes("UTF-8"), key, keyPathName.getBytes())//设置存证内容，加密key，key path
                .setTimestamp(System.currentTimeMillis())//设置业务时间
                .setCategory(catagory)//设置业务分类
                .build();

        // 发送Transaction
        Response<TransactionDO> response = client.sendTransaction(tx);

        if (response.isSuccess()) {
            // 发送成功，业务系统保留Transaction Hash与业务数据关联
            return tx.getTxHashValue();
        } else {
            // 发送失败，抛异常。业务系统应该重试，rpc调用失败时区块链的状态是未知的。
            throw new RuntimeException("写入区块链失败");
        }

    }


    public TransactionDO saveTransactionWrite3() throws IOException, TimeoutException {
        // 加载client配置文件
        Properties p = new Properties();
        p.load(new FileInputStream(QuKuaiUtil.class.getResource("/client.properties").getPath()));
        ClientConfig config = new ClientPropertyConfig(p);

// 使用指定client配置初始化client
        Client client = new Client(config);

// 使用TableSchemaData封装上链数据
        TableSchemaData tableSchemaData = new TableSchemaData();

// 设置数据表名
        tableSchemaData.setTableName("accountLog");

// 设置上链字段名称及数据
        Map<String, String> map = new HashMap<String, String>();
        map.put("from", "Alice");
        map.put("to", "Bob");
        map.put("value", "2000cent");
        map.put("date", "2017-10-10");
        tableSchemaData.setContent(map);

// 编码TableSchemaData
        byte[] accountlog = tableSchemaData.marshal();

// create a ContentOnlyNotary transaction
        TransactionDO tx = TransactionBuilder.getContentOnlyNotaryPayloadBuilder()
                .setContent(accountlog)
                .build();

// 发送Transaction
        Response<TransactionDO> response = client.sendTransaction(tx);


        if (response.isSuccess()) {
            // 发送成功，业务系统保留Transaction Hash与业务数据关联
            return tx;
        } else {
            // 发送失败，抛异常。业务系统应该重试，rpc调用失败时区块链的状态是未知的。
            throw new RuntimeException("写入区块链失败");
        }

    }

    public String  encryptedBlockChain2() throws IOException, TimeoutException, UnsupportedEncodingException {
        Properties p = new Properties();
        p.load(new FileInputStream(QuKuaiUtil.class.getResource("/client.properties").getPath()));
        ClientConfig config = new ClientPropertyConfig(p);
        //上链数据格式:
        //
        // 1. 使用隐私模型
        // 2. 提交者签名交易
        // 3. 使用TableSchemaData装载数据

        //~~~~~~~~~~ 前置数据准备 ~~~~~~~~~~
        // 1. 签名所需要的公私钥对
        // 2. 数据加密所需要的密钥(KeyRepositry)

        // 加载公钥以及私钥
        // load public key
        PublicKey publicKey = PublicKey.loadECDSAFormX509EncodedKey(
                "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8BP572pubO+ItxAbCZLkIavo0T/P"
                        + "Hn98MeXu5mGYyysbcZw/jps2jcOvohk/hFwefvyxlBb5u3T0ICZSiEOSBQ==");

        // load private key
        PrivateKey privateKey = PrivateKey.loadECDSAFromPKCS8EncodedKey(
                "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgZPc9TtXLw/i5ydtR"
                        + "hI6FFptpUO/cUiBTufUkk8eNCvShRANCAATwE/nvam5s74i3EBsJkuQhq+jRP88e"
                        + "f3wx5e7mYZjLKxtxnD+OmzaNw6+iGT+EXB5+/LGUFvm7dPQgJlKIQ5IF");

        // 加载key repositry
        // 初始化链上加密key repositry
        KeyRepositry keyRepositry = new KeyRepositry();

        // A类业务的keyName
        String keyName_a = "/root/biz_a";
        // 为A类业务设置加密key
        keyRepositry.setKey(keyName_a, getKey());

        //~~~~~~~~~~ A类数据上链 ~~~~~~~~~~

        // 确定A类数据的category
        // 该例子表示，高4位0001表示业务大类，低4位0001表示业务子类型,及A类业务
        int category_A = 0x00010001;

        // 确定A类数据的tableName
        String tableName_A = "biz_A";

        // 使用TableSchemaData封装上链数据
        TableSchemaData tableSchemaData = new TableSchemaData();

        // 设置数据表名
        tableSchemaData.setTableName(tableName_A);

        // 设置上链字段名称及数据
        Map<String, String> map = new HashMap<String, String>();
        map.put("from", "Alice");
        map.put("to", "Bob");
        map.put("value", "2000cent");
        map.put("date", String.valueOf(new Date().getTime()));// 时间字段建议使用时间戳
        tableSchemaData.setContent(map);

        // 编码TableSchemaData
        byte[] accountlog = tableSchemaData.marshal();

        // 这里举例,继续以用户维度对log进行加密,设置这条流水的加密keyName
        String logKeyName = keyName_a + "/Alice";
        // 根据key path生成加密key
        byte[] key = keyRepositry.getKeyFor(logKeyName);


        // 使用隐私模型,且签名交易
        // build transaction
        TransactionDO tx = TransactionBuilder.getEncryptShareNotaryPayloadBuilder()
                .encryptContent(accountlog, key, logKeyName.getBytes("UTF-8"))//设置存证内容，加密key，key path
                .setTimestamp(System.currentTimeMillis())//设置业务时间
                .setCategory(category_A)//设置业务分类
                .setCommitterPubKey(publicKey)//设置提交者公钥
                .sign(privateKey);//提交者私钥签名

        Client client = new Client(config);

        // 发送Transaction
        Response<TransactionDO> response = client.sendTransaction(tx);

        System.out.println(response);
        if (response.isSuccess()) {
            // 发送成功，业务系统保留Transaction Hash与业务数据关联
            return tx.getTxHashValue();
        } else {
            // 发送失败，抛异常。业务系统应该重试，rpc调用失败时区块链的状态是未知的。
            throw new RuntimeException("写入区块链失败");
        }


        //~~~~~~~~~~ B类数据上链 ~~~~~~~~~~
        // 以此类推


        //~~~~~~~~~~ 使用总结 ~~~~~~~~~~

        //使用区块链存证前,需要提前确定以下信息

        //你是数据的提交者,对链上的数据需要进行签名。首先需要有公私钥对
        //1. 准备好你的应用的公私钥对,可使用以下工具类生成
        //    * RSANumberKeySpec.genRSAKeyPair() RSA密钥对
        //    * ECDSANumberKeySpec.genECKeyPair() EC密钥对【推荐】


        //业务分类
        //对上链的数据根据业务维度进行规划, 明确每类业务数据的结构及标示,届时给使用方的时候,告诉对方这些信息即可。
        //对于每类业务,确定以下属性
        //1. category 每类业务数据使用对应的值表示
        //2. tableName 每类业务数据使用tablename
        //2. table字段及值的格式 确定每类业务所拥有的字段及值的格式(如时间字段使用时间戳)

        //加密密钥派生规则
        //对于每类业务,确定加密维度,简单理解,每笔数据都有一个虚拟的目录,这里需要设计数据的虚拟加密目录
        // 举例,如合同,使用以下维度加密
        //      /APP_ROOT/contract/contract_id
        // 以上路径表示在应用系统APP中,合同使用contract目录,对于每笔数据,存在contract_id下面
    }


    /**
     * 区块链存入 加密调用方法
     * @param map 传入区块链的数据装换成的map对象
     * @param keyName  区块链对应表的目录
     * @param tableName 区块链对应的的表名
     * @param category 该例子表示，高4位0001表示业务大类，低4位0001表示业务子类型,及A类业务
     *                 高位固定0001，表示租赁平台，地位0001表示合同，0002表示房源
     *                 0x00010001表示合同  0x00010002表示房源 依次类推
     * @return
     * @throws IOException
     * @throws TimeoutException
     * @throws UnsupportedEncodingException
     */
    public String  encryptedBlockChain(Map<String, String> map,String keyName,String tableName,int category) throws IOException, TimeoutException, UnsupportedEncodingException {
        Properties p = new Properties();
        p.load(new FileInputStream(QuKuaiUtil.class.getResource("/client.properties").getPath()));
        ClientConfig config = new ClientPropertyConfig(p);

        PublicKey publicKey = PublicKey.loadECDSAFormX509EncodedKey(
                "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8BP572pubO+ItxAbCZLkIavo0T/P"
                        + "Hn98MeXu5mGYyysbcZw/jps2jcOvohk/hFwefvyxlBb5u3T0ICZSiEOSBQ==");

        // load private key
        PrivateKey privateKey = PrivateKey.loadECDSAFromPKCS8EncodedKey(
                "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgZPc9TtXLw/i5ydtR"
                        + "hI6FFptpUO/cUiBTufUkk8eNCvShRANCAATwE/nvam5s74i3EBsJkuQhq+jRP88e"
                        + "f3wx5e7mYZjLKxtxnD+OmzaNw6+iGT+EXB5+/LGUFvm7dPQgJlKIQ5IF");


        KeyRepositry keyRepositry = new KeyRepositry();


        keyRepositry.setKey(keyName, getKey());


        // 使用TableSchemaData封装上链数据
        TableSchemaData tableSchemaData = new TableSchemaData();

        // 设置数据表名
        tableSchemaData.setTableName(tableName);

        // 设置上链字段名称及数据
//        Map<String, String> map = new HashMap<String, String>();
//        map.put("from", "Alice");
//        map.put("to", "Bob");
//        map.put("value", "2000cent");
//        map.put("date", String.valueOf(new Date().getTime()));// 时间字段建议使用时间戳
        tableSchemaData.setContent(map);

        // 编码TableSchemaData
        byte[] accountlog = tableSchemaData.marshal();

        // 这里举例,继续以用户维度对log进行加密,设置这条流水的加密keyName
        String logKeyName = keyName + "/Alice";
        // 根据key path生成加密key
        byte[] key = keyRepositry.getKeyFor(logKeyName);


        // 使用隐私模型,且签名交易
        // build transaction
        TransactionDO tx = TransactionBuilder.getEncryptShareNotaryPayloadBuilder()
                .encryptContent(accountlog, key, logKeyName.getBytes("UTF-8"))//设置存证内容，加密key，key path
                .setTimestamp(System.currentTimeMillis())//设置业务时间
                .setCategory(category)//设置业务分类
                .setCommitterPubKey(publicKey)//设置提交者公钥
                .sign(privateKey);//提交者私钥签名

        Client client = new Client(config);

        // 发送Transaction
        Response<TransactionDO> response = client.sendTransaction(tx);

        System.out.println(response);
        if (response.isSuccess()) {
            // 发送成功，业务系统保留Transaction Hash与业务数据关联
            return tx.getTxHashValue();
        } else {
            // 发送失败，抛异常。业务系统应该重试，rpc调用失败时区块链的状态是未知的。
            throw new RuntimeException("写入区块链失败");
        }

    }



    private byte[] getKey() {

        try {
            return EncryptTool.genSecretKey();
        } catch (EncryptException e) {
            e.printStackTrace();
            return null;
        }
    }

}




